# Manipular de datos
import pandas as pd
import numpy as np
# Cargar datasets
df15 = pd.read_csv('data/bici15_cdn.csv')
df16 = pd.read_csv('data/bici16_cdn.csv')
df17 = pd.read_csv('data/bici17_cdn.csv')
df18 = pd.read_csv('data/bici18_cdn.csv')
df19 = pd.read_csv('data/bici19_cdn.csv')
# Las columnas son las mismas para todos
df18.columns
# Graficar
import matplotlib.pyplot as plt
import seaborn as sns
# Graficamos un histograma por año para ver cantidad de casos por edad
plt.figure(figsize=(20,10))
plt.subplot(2,2,1)
viajes15 = df15.bici_edad
viajes15.plot.hist(grid=True, bins=20, rwidth=0.9,
color='#2F9599')
plt.title('Recorridos 2015')
plt.subplot(2,2,2)
viajes16 = df16.bici_edad
viajes16.plot.hist(grid=True, bins=20, rwidth=0.9,
color='#F7DB4F')
plt.title('Recorridos 2016')
plt.subplot(2,2,3)
viajes17 = df17.bici_edad
viajes17.plot.hist(grid=True, bins=20, rwidth=0.9,
color='#FF847C')
plt.title('Recorridos 2017')
plt.subplot(2,2,4)
viajes18 = df18.bici_edad
viajes18.plot.hist(grid=True, bins=20, rwidth=0.9,
color='#FECEAB')
plt.title('Recorridos 2018')
plt.suptitle('Distribución de los viajes en bicicleta por edad. CABA 2015-2018', fontsize=15);
# Los grafico aparte porque no cubren todo el año
viajes19 = df19.bici_edad
viajes19.plot.hist(grid=True, bins=20, rwidth=0.9,
color='#607c8e', figsize=(15,5))
plt.title('Recorridos 2019')
plt.xlabel('Edad')
plt.ylabel('Frecuencia')
plt.grid(axis='y', alpha=0.3);
IDEA 1: Quiénes son los usuarios más frecuentes del servicio de bicicletas? Los jóvenes entre 25 y 35 años (distribución con sesgo a la derecha)
# Diferencias de uso por genero (2019)
edad = df19.bici_edad
sexo = df19.bici_sexo
plt.figure(figsize=(15,5))
plt.hist([[e for e, s in zip(edad, sexo) if s=='MASCULINO'],
[e for e, s in zip(edad, sexo) if s=='FEMENINO']],
color=['#FF847C','#ADD8E6'], bins=20, stacked=True)
plt.grid(alpha=0.3)
plt.title('Recorridos 2019 según sexo del usuario')
plt.show();
# Cantidad de hombres y mujeres por sexo (de aca saco la data para los graficos de torta)
df18.groupby(['bici_sexo'])['bici_edad'].count()
# Usuarios por sexo
# Plot 1
labels = ['MASCULINO', 'FEMENINO']
sizes = [391221, 112646]
colors = ['#ADD8E6', '#FF847C']
explode = (0.09, 0)
# Plot 2
sizes2 = [537252, 178962]
# Plot 3
sizes3 = [1402028, 484136]
explode3 = (0.09, 0)
# Plot 4
sizes4 = [2162906, 807495]
# Grilla de plots
plt.figure(figsize=(15,6))
plt.subplot(1,4,1)
plt.pie(sizes, explode=explode, labels=None, colors=colors,
autopct='%1.1f%%', shadow=True, startangle=140, pctdistance = 0.5)
plt.legend(labels, loc=(0.22,0.1))
plt.axis('equal')
plt.title('Usuarios por sexo \n Recorridos CABA-2015', y=0.8)
plt.subplot(1,4,2)
plt.pie(sizes2, explode=explode, labels=None, colors=colors,
autopct='%1.1f%%', shadow=True, startangle=140, pctdistance = 0.5)
plt.legend(labels, loc=(0.22,0.1))
plt.title('Usuarios por sexo \n Recorridos CABA-2016', y=0.8)
plt.axis('equal')
plt.subplot(1,4,3)
plt.pie(sizes3, explode=explode, labels=None, colors=colors,
autopct='%1.1f%%', shadow=True, startangle=140, pctdistance = 0.5)
plt.legend(labels, loc=(0.22,0.1))
plt.title('Usuarios por sexo \n Recorridos CABA-2017', y=0.8)
plt.axis('equal')
plt.subplot(1,4,4)
plt.pie(sizes4, explode=explode, labels=None, colors=colors,
autopct='%1.1f%%', shadow=True, startangle=140, pctdistance = 0.5)
plt.legend(labels, loc=(0.22,0.1))
plt.title('Usuarios por sexo \n Recorridos CABA-2018', y=0.8)
plt.axis('equal')
plt.show();
Los resultados por sexo indican que la mayoría de los usuarios son varones. Un resultado similar encontré en este artículo: https://medium.com/@maglionejm_15007/entendiendo-con-un-poco-m%C3%A1s-de-profundidad-el-sistema-de-movilidad-sustentable-de-la-ciudad-de-f157ea3eb345
from datetime import datetime
# Función para devolver columna de fecha y hora
def fecha_hora(df):
'''
Separa la indormacion de fecha y hora que en todos los df estan juntas como string
Argumentos
----------
df: el dataframe de cada año de recorridos de bicis
'''
# Se splitea la columna en dos: fecha y hora que en todos los df estan juntas como str
fecha_hora = df['bici_Fecha_hora_retiro'].str.split(' ', expand=True)
# Se almacena en un nuevo df
fecha_hora.columns = ['Fecha','Hora']
return fecha_hora
# Función para devolver dia de la semana
def obtener_dia_semana(fecha):
'''
Devuelve el dia de la semana a partir de la fecha
Argumentos
----------
fecha: columna del df, para usarla hay que mapearla porque strptime no toma series
'''
return datetime.strptime(fecha,'%Y-%m-%d').weekday()
# Cómo utilizar ambas funciones en cualquiera de los dataframes
# 1) se almacenan las columnas de fecha y hora
tiempo19 = fecha_hora(df19)
# 2) a partie de la Fecha se obtiene el dia de la semana mapeando la última función
dia19 = tiempo19.Fecha.map(obtener_dia_semana)
# Creo una copia por las dudas
df19b = df19.copy()
# Reemplaza numero de dia por nombre. Se le pasa cualquier de los dataframes
def nombre_dia(df):
dia = df.replace({0:'lunes', 1:'martes', 2:'miercoles', 3:'jueves', 4:'viernes', 5:'sabado', 6:'domingo'})
return dia
# Dias de la semana
df19b['dia_semana'] = nombre_dia(dia19)
# Hora del dia
df19b['hora_dia'] = tiempo19.Hora
# Funcion para clasificar el momento del dia. Se pueden cambiar los horarios si resulta arbitrario
def momento_dia(x):
if (x['hora_dia']>= '07:00:00') & (x['hora_dia']<= '11:30:00'):
return '1.Mañana'
if (x['hora_dia']>='11:30:00') & (x['hora_dia'] <= '14:30:00'):
return '2.Mediodia'
if (x['hora_dia']>'14:30:00') & (x['hora_dia'] < '17:00:00'):
return '3.Media tarde'
if (x['hora_dia']>='17:00:00') & (x['hora_dia'] <= '19:00:00'):
return '4.Tarde'
if (x['hora_dia']>'19:00:00') & (x['hora_dia'] <= '21:00:00'):
return '5.Tarde noche'
if (x['hora_dia']>'21:00:00') & (x['hora_dia'] <= '24:00:00'):
return '6.Noche'
if (x['hora_dia']>'24:00:00') & (x['hora_dia'] < '07:00:00'):
return '7.Madrugada'
momento_dia = df19b.apply(lambda x: momento_dia(x),1)
df19b['momento_dia'] = momento_dia
# Aca se podria ver si al patron cambia filtrando para fines o dias de semana
sexo_dia = df19b.groupby(['momento_dia','bici_sexo'])[['bici_sexo']].count()
sexo_dia.columns = ['Cantidad']
sexo_dia = sexo_dia.reset_index()
plt.figure(figsize=(12,5))
ax = sns.lineplot(x="momento_dia", y="Cantidad", hue='bici_sexo', data=sexo_dia.loc[sexo_dia['bici_sexo']!='NO INFORMADO'], palette = ['#FF847C','#ADD8E6'])
ax.set_facecolor("white")
plt.title('Cantidad de usuarios por sexo segun momento del recorrido. CABA-2019', y=1.05)
plt.grid()
# Total de estaciones de origen
len(df19b.bici_nombre_estacion_origen.unique())
# Total de estaciones de destino
len(df19b.bici_nombre_estacion_destino.unique())
# Geolocalizacion
import geopandas as gpd
import mplleaflet
# Shape de estaciones descargados de DataBA
estaciones = gpd.read_file('data/estaciones_de_bicicletas.shp')
estaciones.NOMBRE.unique()
# Groupby por origen y destino
origen = df19.groupby(['bici_nombre_estacion_origen'])[['bici_sexo']].count().reset_index()
destino = df19.groupby(['bici_nombre_estacion_destino'])[['bici_sexo']].count().reset_index()
# Usamos origen y destino para mergear con el shape de puntos
origen_map = pd.merge(estaciones,origen, left_on='NOMBRE', right_on='bici_nombre_estacion_origen')
destino_map = pd.merge(estaciones,destino, left_on='NOMBRE', right_on='bici_nombre_estacion_destino')
# ORIGEN
fig, ax = plt.subplots(1, figsize = (8,8))
ax.set_axis_off()
origen_map.plot(ax=ax,marker='o', color='#FF847C', markersize=origen_map['bici_sexo']/20)
mplleaflet.display(fig=ax.figure, crs=origen_map.crs)
# DESTINO
fig, ax = plt.subplots(1, figsize = (8,8))
ax.set_axis_off()
destino_map.plot(ax=ax,marker='o', color='#2F9599', markersize=destino_map['bici_sexo']/20 )
mplleaflet.display(fig=ax.figure, crs=destino_map.crs)
# radios censales caba
caba = gpd.read_file('data/informacion_censal_por_radio_2010.shp')
f, ax = plt.subplots(1,figsize=(15,8))
caba.to_crs(crs=origen_map.crs).plot(ax=ax, color='#dadada')
origen_map.plot(ax=ax,marker='o', color='#FF847C', markersize=origen_map['bici_sexo']/20 )
plt.axis('equal')
ax.set_axis_off()
#!pip3 install plotly_express
# Armamos la tabla para el heatmap. Aca serian usuarios totales
# por hora de retiro. Se puede hacer con otras variables (minutos promedio por hora de retiro)
# Horas en eje x: Cantidad de retiros por hora segun dia de la semana
pivot1 = pd.pivot_table(df19b, values="bici_sexo",index=pd.to_datetime(df19b.bici_Fecha_hora_retiro).dt.weekday,
columns=pd.to_datetime(df19b.bici_Fecha_hora_retiro).dt.hour, aggfunc = 'count',fill_value=0)
# Horas en eje y
pivot2 = pd.pivot_table(df19b, values="bici_sexo",index=pd.to_datetime(df19b.bici_Fecha_hora_retiro).dt.hour,
columns=["dia_semana"], aggfunc = 'count',fill_value=0)
# Graficamos un heatmap con el pivot1
plt.figure(figsize=(15,4))
ax = sns.heatmap(pivot1, square=True, cmap=sns.diverging_palette(180, 10, as_cmap=True), linewidths=.1)
plt.setp(ax.xaxis.get_majorticklabels(), rotation=360 )
# Eje x
ax.set_xlabel("Hora del día", labelpad = 12)
labels = [item.get_text()+''+'hs' for item in ax.get_xticklabels()]
ax.set_xticklabels(labels)
# Eje y
ax.set_yticklabels(['Lunes','Martes','Miercoles','Jueves','Viernes','Sabado','Domingo'], rotation = 360)
ax.set_ylabel("")
ax.set_title('Retiro de bicicletas (en cantidad de personas) por hora . CABA-2019', y=1.05)
plt.tight_layout()
plt.show();
# Creamos un pivot para cada año.
#2015
pivot15 = pd.pivot_table(df15, values="bici_sexo",index=pd.to_datetime(df15.bici_Fecha_hora_retiro).dt.hour,
columns=pd.to_datetime(df15.bici_Fecha_hora_retiro).dt.weekday, aggfunc = 'count',fill_value=0)
#2016
pivot16 = pd.pivot_table(df16, values="bici_sexo",index=pd.to_datetime(df16.bici_Fecha_hora_retiro).dt.hour,
columns=pd.to_datetime(df16.bici_Fecha_hora_retiro).dt.weekday, aggfunc = 'count',fill_value=0)
#2017
pivot17 = pd.pivot_table(df17, values="bici_sexo",index=pd.to_datetime(df17.bici_Fecha_hora_retiro).dt.hour,
columns=pd.to_datetime(df17.bici_Fecha_hora_retiro).dt.weekday, aggfunc = 'count',fill_value=0)
#2018
pivot18 = pd.pivot_table(df18, values="bici_sexo",index=pd.to_datetime(df18.bici_Fecha_hora_retiro).dt.hour,
columns=pd.to_datetime(df18.bici_Fecha_hora_retiro).dt.weekday, aggfunc = 'count',fill_value=0)
#2019
pivot19 = pd.pivot_table(df19, values="bici_sexo",index=pd.to_datetime(df19.bici_Fecha_hora_retiro).dt.hour,
columns=pd.to_datetime(df19.bici_Fecha_hora_retiro).dt.weekday, aggfunc = 'count',fill_value=0)
# Graficamos un heatmap con el equivalente a pivot2. Se grafican los años anteriores
fig = plt.figure(figsize=(24,10))
ax1 = fig.add_subplot(1,4,1)
ax2 = fig.add_subplot(1,4,2)
ax3 = fig.add_subplot(1,4,3)
ax4 = fig.add_subplot(1,4,4)
#2015
sns.heatmap(pivot15, square=True, cmap=sns.diverging_palette(180, 10, as_cmap=True), linewidths=.1, ax = ax1)
plt.setp(ax1.xaxis.get_majorticklabels(), rotation=360 )
ax1.set_ylabel("Hora del día", labelpad = 12)
labels1 = [item.get_text()+''+'hs' for item in ax1.get_yticklabels()]
ax1.set_yticklabels(labels, rotation = 0)
ax1.set_ylabel("")
ax1.set_xticklabels(['Lu','Ma','Mi','Ju','Vi','Sa','Do'], rotation = 360)
ax1.set_xlabel("")
ax1.set_title('2015', y=1.01)
#2016
sns.heatmap(pivot16, square=True, cmap=sns.diverging_palette(180, 10, as_cmap=True), linewidths=.1, ax = ax2)
plt.setp(ax1.xaxis.get_majorticklabels(), rotation=360 )
ax2.set_ylabel("Hora del día", labelpad = 12)
labels2 = [item.get_text()+''+'hs' for item in ax2.get_yticklabels()]
ax2.set_yticklabels(labels, rotation = 0)
ax2.set_ylabel("")
ax2.set_xticklabels(['Lu','Ma','Mi','Ju','Vi','Sa','Do'], rotation = 360)
ax2.set_xlabel("")
ax2.set_title('2016', y=1.01)
#2017
sns.heatmap(pivot17, square=True, cmap=sns.diverging_palette(180, 10, as_cmap=True), linewidths=.1, ax = ax3)
plt.setp(ax3.xaxis.get_majorticklabels(), rotation=360 )
ax3.set_ylabel("Hora del día", labelpad = 12)
labels3 = [item.get_text()+''+'hs' for item in ax3.get_yticklabels()]
ax3.set_yticklabels(labels, rotation = 0)
ax3.set_ylabel("")
ax3.set_xticklabels(['Lu','Ma','Mi','Ju','Vi','Sa','Do'], rotation = 360)
ax3.set_xlabel("")
ax3.set_title('2017', y=1.01)
#2018
sns.heatmap(pivot18, square=True, cmap=sns.diverging_palette(180, 10, as_cmap=True), linewidths=.1, ax = ax4)
plt.setp(ax4.xaxis.get_majorticklabels(), rotation=360 )
ax4.set_ylabel("Hora del día", labelpad = 12)
labels4 = [item.get_text()+''+'hs' for item in ax4.get_yticklabels()]
ax4.set_yticklabels(labels, rotation = 0)
ax4.set_ylabel("")
ax4.set_xticklabels(['Lu','Ma','Mi','Ju','Vi','Sa','Do'], rotation = 360)
ax4.set_xlabel("")
ax4.set_title('2018', y=1.01)
fig.suptitle('Retiro de bicicletas (en cantidad de personas) por hora . CABA-2015/2018', fontsize=16)
plt.show();
# Comparamos para el mismo año un meses más calurosos y más fríos
# Creamos una funcion para devolver el año y filtrar en cada dataframe por mes
def mes(df):
mes = pd.to_datetime(df18.bici_Fecha_hora_retiro).dt.month
df['mes'] = mes
return df
df17b = mes(df17.copy())
df18b = mes(df18.copy())
# Generamos las tablas pivot para meses de calor y frio en cada año
#2017 - meses de frio
pivot17f = pd.pivot_table(data=df17b.loc[(df17b['mes']>=6) & (df17b['mes']<=8)], values="bici_sexo",index=pd.to_datetime(df17b.bici_Fecha_hora_retiro).dt.hour,
columns=pd.to_datetime(df17b.bici_Fecha_hora_retiro).dt.weekday, aggfunc = 'count',fill_value=0)
#2017 - meses de calor
pivot17c = pd.pivot_table(data=df17b.loc[(df17b['mes']>=9) & (df17b['mes']<=11)], values="bici_sexo",index=pd.to_datetime(df16.bici_Fecha_hora_retiro).dt.hour,
columns=pd.to_datetime(df16.bici_Fecha_hora_retiro).dt.weekday, aggfunc = 'count',fill_value=0)
#2018 - meses de frio
pivot18c = pd.pivot_table(data=df18b.loc[(df18b['mes']>=6) & (df18b['mes']<=8)], values="bici_sexo",index=pd.to_datetime(df17.bici_Fecha_hora_retiro).dt.hour,
columns=pd.to_datetime(df17.bici_Fecha_hora_retiro).dt.weekday, aggfunc = 'count',fill_value=0)
#2018 - meses de calor
pivot18f = pd.pivot_table(data=df18b.loc[(df18b['mes']>=9) & (df18b['mes']<=11)], values="bici_sexo",index=pd.to_datetime(df18.bici_Fecha_hora_retiro).dt.hour,
columns=pd.to_datetime(df18.bici_Fecha_hora_retiro).dt.weekday, aggfunc = 'count',fill_value=0)
# Graficamos un heatmap con el equivalente a pivot2. Se grafican los años anteriores
fig = plt.figure(figsize=(24,10))
ax1 = fig.add_subplot(1,4,1)
ax2 = fig.add_subplot(1,4,2)
ax3 = fig.add_subplot(1,4,3)
ax4 = fig.add_subplot(1,4,4)
#2017 - meses de frio
sns.heatmap(pivot17f, square=True, cmap='coolwarm', linewidths=.1, ax = ax1)
plt.setp(ax1.xaxis.get_majorticklabels(), rotation=360 )
ax1.set_ylabel("Hora del día", labelpad = 12)
labels1 = [item.get_text()+''+'hs' for item in ax1.get_yticklabels()]
ax1.set_yticklabels(labels1, rotation = 0)
ax1.set_ylabel("")
ax1.set_xticklabels(['Lu','Ma','Mi','Ju','Vi','Sa','Do'], rotation = 360)
ax1.set_xlabel("")
ax1.set_title('Junio/Agosto - 2017', y=1.01)
#2017 - meses de calor
sns.heatmap(pivot17c, square=True, cmap='Spectral_r', linewidths=.1, ax = ax2)
plt.setp(ax1.xaxis.get_majorticklabels(), rotation=360 )
ax2.set_ylabel("Hora del día", labelpad = 12)
#labels2 = [item.get_text()+''+'hs' for item in ax2.get_yticklabels()]
labels2 = [str(int(i))+''+'hs' for i in pivot17c.index]
ax2.set_yticklabels(labels2, rotation = 0)
ax2.set_ylabel("")
ax2.set_xticklabels(['Lu','Ma','Mi','Ju','Vi','Sa','Do'], rotation = 360)
ax2.set_xlabel("")
ax2.set_title('Septiembre/Noviembre - 2017', y=1.01, x=0.55)
#2018 - meses de frio
sns.heatmap(pivot18f, square=True, cmap='coolwarm', linewidths=.1, ax = ax3)
plt.setp(ax3.xaxis.get_majorticklabels(), rotation=360 )
ax3.set_ylabel("Hora del día", labelpad = 12)
labels3 = [item.get_text()+''+'hs' for item in ax3.get_yticklabels()]
ax3.set_yticklabels(labels3, rotation = 0)
ax3.set_ylabel("")
ax3.set_xticklabels(['Lu','Ma','Mi','Ju','Vi','Sa','Do'], rotation = 360)
ax3.set_xlabel("")
ax3.set_title('Junio/Agosto - 2018', y=1.01)
#2018 - meses de calor
sns.heatmap(pivot18c, square=True, cmap='Spectral_r', linewidths=.1, ax = ax4)
plt.setp(ax4.xaxis.get_majorticklabels(), rotation=360 )
ax4.set_ylabel("Hora del día", labelpad = 12)
#labels4 = [item.get_text()+''+'hs' for item in ax4.get_yticklabels()]
labels4 = [str(int(i))+''+'hs' for i in pivot18c.index]
ax4.set_yticklabels(labels4, rotation = 0)
ax4.set_ylabel("")
ax4.set_xticklabels(['Lu','Ma','Mi','Ju','Vi','Sa','Do'], rotation = 360)
ax4.set_xlabel("")
ax4.set_title('Septiembre/Noviembre - 2018', y=1.01, x=0.55)
fig.suptitle('Retiro de bicicletas (en cantidad de personas) por hora . Meses frios vs templados. CABA-2017/2018', fontsize=16)
plt.show();
import plotly_express as px
# PLotly para graficos dinamicos
#https://nbviewer.jupyter.org/github/plotly/plotly_express/blob/gh-pages/walkthrough.ipynb
# Histograma con tiempo promedio de uso por dia de semana
px.histogram(df19b, x="dia_semana", y="bici_tiempo_uso", histfunc="avg")
# Creamos la variable hora en un df al azar (2018)
df18b['hora'] = pd.to_datetime(df18b.bici_Fecha_hora_retiro).dt.hour
# Valores agrupados para 2018 segun estacion y origen
z = df18b.groupby(['bici_nombre_estacion_origen','hora'])['bici_sexo'].count().reset_index()
z.head()
# Recuperamos del shape de estaciones la latitud y la longitud
estaciones['x'], estaciones['y']=estaciones.geometry.x, estaciones.geometry.y
# Joineamos los valores de latitud y longitud al dataframe "z"
v = pd.merge(z,estaciones[['NOMBRE','x','y']], right_on='NOMBRE', left_on='bici_nombre_estacion_origen')
# Este scatter muestra "por hora" la evolucion de la cantidad de usuarios por estacion (ubicada por lat y lon)
# Dato: conforme avanzan las horas, aumenta la cantidad de usuarios. Sobretodo a partir del mediodia
# Inquietud: clasificar color de puntos por barrio o algun otro parametro espacial (como si esta en microcentro o no)
px.scatter(v, x="x", y="y",size="bici_sexo", color="NOMBRE", hover_name="NOMBRE",
animation_frame="hora", animation_group="NOMBRE",
labels=dict(x="Longitud", y="Latitud", NOMBRE = 'Estacion', hora = 'Hora', bici_sexo = 'Usuarios'))
# Este scatter muestra "por hora" la evolucion de los minutos promedio de uso y de la edad promedio
# Curioso: conforme avanzan las horas los promedios (puntos) se aglomeran en las edades mas altas
# Dataframe
w1 = df18b.loc[df18b['bici_sexo']!='NO INFORMADO'].groupby(['bici_nombre_estacion_origen','hora','bici_sexo'])['bici_edad','bici_tiempo_uso'].mean().reset_index()
# Plot
px.scatter(w1, x="bici_edad", y="bici_tiempo_uso",
animation_frame="hora", animation_group="bici_nombre_estacion_origen",
labels=dict(bici_edad="Edad media", bici_tiempo_uso="Tiempo medio de uso", NOMBRE = 'Estacion', hora = 'Hora', bici_sexo = 'Usuarios')
)
# Inquietud: los puntos se aglomeran (menor dispersion) en edades y tiempos medios de uso mas chicos. Presumiblementte es gente que usa la bici para ir y volver del laburo (porque son tiempos de uso mas cortos y franja etaria cercana a los treinta).
# Inquietud 2: clasificar el dataframe en "horario laboral" y "no laboral". Colorear los puntos en base a eso